Swift 中 闭包 与 逃逸闭包 的区别
在 Swift 中,闭包 是一种自包含的功能块,可以在代码中被传递和使用。而 逃逸闭包(Escaping Closure) 是一种特殊类型的闭包,它可以在定义它的函数返回之后仍然被调用。
这两者的区别主要体现在闭包的生命周期和使用场景上。
闭包与逃逸闭包的主要区别
特性 | 普通闭包(非逃逸闭包) | 逃逸闭包 |
---|---|---|
生命周期 | 必须在函数作用域内完成执行 | 可以在函数返回后被调用 |
定义方式 | 默认行为 | 需显式声明为 @escaping |
使用场景 | 函数内部的短期任务 | 异步任务或需要延迟调用的任务 |
闭包捕获规则 | 不需要持久化捕获上下文中的变量 | 闭包可能需要持久化上下文变量的捕获 |
内存管理 | 不会引起循环引用 | 容易引起循环引用(需要使用 [weak self] ) |
普通闭包
普通闭包在函数调用过程中完成执行,其生命周期受限于函数本身,函数结束后闭包就被销毁。
示例:普通闭包
func performAction(action: () -> Void) {
print("Before action")
action()
print("After action")
}
performAction {
print("Executing action")
}
输出:
Before action
Executing action
After action
- 闭包
action
在performAction
函数体内执行,函数返回后不再使用。 - 不需要显式声明
@escaping
。
逃逸闭包
逃逸闭包在函数返回之后仍然可能被调用,例如异步操作中。因为它超出了函数作用域,因此必须显式声明为 @escaping
。
示例:逃逸闭包
func performAsyncAction(completion: @escaping () -> Void) {
print("Starting async operation")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completion() // 闭包在函数返回后被调用
}
print("Async operation scheduled")
}
performAsyncAction {
print("Async operation completed")
}
输出:
Starting async operation
Async operation scheduled
Async operation completed
- 闭包
completion
被存储并在异步操作完成后执行。 - 必须用
@escaping
标记,否则编译器会报错。
逃逸闭包的应用场景
- 异步任务: 网络请求、定时器、线程切换等。
- 事件回调: 按钮点击、手势响应等需要延迟执行的操作。
捕获上下文和内存管理
普通闭包的捕获行为
普通闭包通常不会导致循环引用,因为其生命周期与函数作用域一致。
func calculate() {
let value = 10
performAction {
print("Value is \(value)") // 捕获了变量 `value`
}
}
逃逸闭包的捕获行为
逃逸闭包可能会导致循环引用,特别是在捕获 self
的情况下。为避免此问题,应该使用 [weak self]
或 [unowned self]
。
class MyClass {
var value = 0
func performAsyncTask() {
performAsyncAction { [weak self] in
guard let self = self else { return }
print("Value is \(self.value)")
}
}
}
总结
- 普通闭包:仅在函数内执行,不需要
@escaping
标记。 - 逃逸闭包:函数返回后可能被调用,必须使用
@escaping
标记。 - 使用逃逸闭包时需特别注意内存管理,防止捕获循环引用的问题。